Scuba diving log data wrestling

Scuba diving
dive log
garmin dive watch
Creating a dive_log.xlsx from garmin .fit files using data wrestling
Author

Pablo Fuenzalida

Published

July 5, 2026

How to summarise garmin dive computer data

My website may have been coded by robots - but this wasn’t!

Diving for a job can be tricky. When you’re applying for new opportunities they normally want some form of log for you to show them you can actually dive.

It makes life easier if you have a dive computer. lucky, my tax write-off last year was to upgrade my dive computer to a garmin descent 2 since I flooded my last one. I did use this daily and absoulely love the computer. It has tides, moon info, lets you change songs, tap-and-pay, and every dive configuration you could want. From single-gas rec diving to CCR and everything inbetween.

If you are reading this I will assume you know how to use R, and that you have a folder somewhere in your laptop full of .fit files, the garmin files that contain info of each dive.

To learn more on how to export them, google it! I just used my garmin dive app and on each dive individually exported them.

https://www8.garmin.com/manuals/webhelp/GUID-4A2D3019-BA14-49BD-B371-4214D187D493/EN-

FYI - We will use a package graciously created by predecessors:

https://github.com/grimbough/FITfileRhttps://github.com/grimbough/FITfileR

Thank you grimbough : )

if(!requireNamespace("remotes")) { # install remotes if you don't have 
  install.packages("remotes")
}
remotes::install_github("grimbough/FITfileR")

library(FITfileR) # the package that saves us
library(dplyr) # for data wrestling
library(purrr) # for processing a function

# specify where your dives are
fit_folder <- "~/Documents/Resume/dive_log"
# read all the files
files <- list.files(fit_folder, pattern = "\\.fit$", full.names = TRUE)

# write a lil function
read_fit_records <- function(file) {
  fit <- readFitFile(file) # to read fit files
  
  recs <- getMessagesByType(fit, "record") # turn messages into records
  
  # recs is already a list of data.frames/tibbles
  df <- bind_rows(recs) # bind the rowss of each dataframe
  
  df$source_file <- basename(file) # source_file as a col for basename
  
  df
}

all_records <- map(files, read_fit_records) %>% # map from purrr 
  bind_rows() # combine all data into one df


dat <- all_records |> 
  group_by(source_file) |>  # group by dive 
  mutate(date = as.Date(timestamp), # date
         time_in  = min(timestamp, na.rm = TRUE) + lubridate::hours(10), # time in (UTC + 10)
         time_out = max(timestamp, na.rm = TRUE) + lubridate::hours(10), # time out (UTC +10)
         dive_mins = round(as.numeric(difftime(time_out, time_in, units ="mins")),2), # total dive time 
         max_depth = round(max(depth, na.rm = T), 2), # depth
         temperature = round(mean(temperature, na.rm = T), 2), # temperature
         .groups = "drop") |>  # ungroup
  distinct(date, time_in, time_out,dive_mins, max_depth, temperature) # drop unnecessary data

str(dat) 
gropd_df [47 × 7] (S3: grouped_df/tbl_df/tbl/data.frame)
 $ source_file: chr [1:47] "408 Bundaberg Single-Gas Dive.fit" "409 Single-Gas Dive.fit" "410 Single-Gas Dive.fit" "411 Single-Gas Dive.fit" ...
 $ date       : Date[1:47], format: "2026-01-15" "2026-01-16" ...
 $ time_in    : POSIXct[1:47], format: "2026-01-16 08:04:17" "2026-01-16 13:59:23" ...
 $ time_out   : POSIXct[1:47], format: "2026-01-16 08:37:59" "2026-01-16 14:59:58" ...
 $ dive_mins  : num [1:47] 33.7 60.6 49 52.6 62.2 ...
 $ max_depth  : num [1:47] 10.9 15.7 13.8 22.3 20.5 ...
 $ temperature: num [1:47] 27.5 27.1 27 27.9 28.4 ...
 - attr(*, "groups")= tibble [47 × 2] (S3: tbl_df/tbl/data.frame)
  ..$ source_file: chr [1:47] "408 Bundaberg Single-Gas Dive.fit" "409 Single-Gas Dive.fit" "410 Single-Gas Dive.fit" "411 Single-Gas Dive.fit" ...
  ..$ .rows      : list<int> [1:47] 
  .. ..$ : int 1
  .. ..$ : int 2
  .. ..$ : int 3
  .. ..$ : int 4
  .. ..$ : int 5
  .. ..$ : int 6
  .. ..$ : int 7
  .. ..$ : int 8
  .. ..$ : int 9
  .. ..$ : int 10
  .. ..$ : int 11
  .. ..$ : int 12
  .. ..$ : int 13
  .. ..$ : int 14
  .. ..$ : int 15
  .. ..$ : int 16
  .. ..$ : int 17
  .. ..$ : int 18
  .. ..$ : int 19
  .. ..$ : int 20
  .. ..$ : int 21
  .. ..$ : int 22
  .. ..$ : int 23
  .. ..$ : int 24
  .. ..$ : int 25
  .. ..$ : int 26
  .. ..$ : int 27
  .. ..$ : int 28
  .. ..$ : int 29
  .. ..$ : int 30
  .. ..$ : int 31
  .. ..$ : int 32
  .. ..$ : int 33
  .. ..$ : int 34
  .. ..$ : int 35
  .. ..$ : int 36
  .. ..$ : int 37
  .. ..$ : int 38
  .. ..$ : int 39
  .. ..$ : int 40
  .. ..$ : int 41
  .. ..$ : int 42
  .. ..$ : int 43
  .. ..$ : int 44
  .. ..$ : int 45
  .. ..$ : int 46
  .. ..$ : int 47
  .. ..@ ptype: int(0) 
  ..- attr(*, ".drop")= logi TRUE
sum(dat$dive_mins, na.rm = TRUE) # how many mins total dive time did we have
[1] 2497.89
# x mins divided by 60 = hours
openxlsx::write.xlsx(dat, "pfuenzalida_dives_ladyelliot.xlsx") 
# write it into an excel sheet